/*
 * Copyright (c) 1999-2004 eVelopers Corporation. All rights reserved.
 *
 * This is open source software; you can use, redistribute and/or modify 
 * it under the terms of the Open Software Licence v 2.1 as published by the Open 
 * Source Initiative.
 *
 * You should have received a copy of the Open Software Licence along with this
 * application; if not, contact the Open Source Initiative (http://opensource.org).
 */
package com.unimod.fsml.validation.problems;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.ui.IMarkerResolution;

import com.unimod.fsml.model.lexer.Token;
import com.unimod.fsml.validation.ProblemToken;

/**
 * <p>
 * Represents any compilation or validation problem in model.
 * Knows about state machine that has problem, about host model element - "entry point" for problem source, about additional 
 * model elements that participate in problem (contains host model element also), about Eclipse IMarker.  
 * Forces it's children to implement IMarkerResolutionGenerator2 interface. This interface 
 * allows Eclipse to suggest to user possible problem resolutions.
 * </p>
 * <p>
 * Possible problems are shown in table below.
 * <table border="1">
 * <tr><td><strong>Problem name</strong></td><td><strong>Problem description</strong></td><td><strong>Associated model elements and additional problem descriptors</strong></td></tr>
 * <tr><td colspan="3"><strong><center>Syntactic problems</center></strong></td></tr>
 * <tr><td>Empty name</td><td>model element has null name</td><td>Model element with empty name</td></tr>
 * <tr><td>Empty implementation class name</td><td>controlled object handler has null implementation class name</td><td>Controlled object handler with empty implemenation class name</td></tr>
 * <tr><td>Empty state type</td><td>state has null state type. Usually such kind of error should never appear</td><td>State</td></tr>
 * <tr><td>Empty guard condition</td><td>transition has not null event, but null guard condition</td><td>Transition</td></tr>
 * <tr><td>Incorrect action identifier</td><td> null action identifier or incorrect name of controlled object handler or incorrect name of controlled object method</td><td>Action</td></tr>
 * <tr><td>Incorrect expression</td><td>guard expression has incorrect format</td><td>Transition, Guard, ParserException</td></tr>
 * <tr><td colspan=3><center><strong>Semantic problems</center></strong></td></tr>
 * <tr><td>Unattainable state</td><td>state is unattainable</td><td>State</td></tr>
 * <tr><td>Incomplete guard set</td><td>incomplete guard conditions set found</td><td>Source State of Transitions with incomplete guard conditions set, Event on transitions with incomplete guard set, Guard residue of the guard set. If transition from the
     given State with given Event and with given Guard will be added then guard set became complete</td></tr>
 * <tr><td>Inconsistent guard set</td><td>inconsistent guards for two transitions found</td><td>Both Transitions in inconsistent pair. Guard intersection of the guards on transitions. If guard on the one of the given transitions will be conjunced with given Guard the system will be consistent.</td></tr>
 * <tr><td>Redundant else transitions</td><td>more then one else transition for state found</td><td>Composite State with more then one initial substates</td></tr>
 * <tr><td>No initial state</td><td>Composite state has no initial state</td><td>Composite State</td></tr>
 * </table>
 * </p>
 */
public abstract class Problem {
	
	private IMarker marker;
	protected ProblemToken[] problematic;
	protected ProblemToken hostToken;
    private Map<ProblemToken, Map<String, List<Range>>> problematic2properties = new HashMap<ProblemToken, Map<String, List<Range>>>();

    Problem(IMarker marker, ProblemToken host) {
        this(marker, host, new ProblemToken[] {host});
    }

	/**
	 * Creates new Problem.
	 * 
	 * @param marker eclipse IMarker
	 * @param stateMachine state machine that contains problem 
	 * @param host host model element of problem
	 * @param p array of model elements that associated with problem
	 */
	Problem(IMarker marker, ProblemToken host, ProblemToken[] p) {
		this.marker = marker;
		this.hostToken = host;
		this.problematic = p;
		setMarkerAttributes();
	}

	protected void setMarkerAttributes() {
		try {
			Token token = hostToken.getToken();
			marker.setAttributes(
					new String[] {IMarker.MESSAGE, IMarker.SEVERITY, IMarker.TRANSIENT, 
							IMarker.CHAR_START, IMarker.CHAR_END, IMarker.LINE_NUMBER},
					new Object[] {getDescription(), new Integer(getSeverity()), new Boolean(true), 
							token.getStart(),  token.getEnd(), token.getLineNumber()});
		} catch (CoreException e) {
			e.printStackTrace();
			
			throw new RuntimeException("Unexpected error inside Problem.setMarkerAttributes()", e);
		}				
	}

	public ProblemToken getHostToken() {
		return hostToken;
	}
	
	/**
	 * @return Returns the GProblematic.
	 */
	public ProblemToken[] getProblemTokens() {
		return problematic;
	}
	
	/**
	 * @return Returns the marker.
	 */
	public IMarker getMarker() {
		return marker;
	}
	
	public List<Range> getPropertyFeedback(ProblemToken p, String propertyId) {
        Map<String, List<Range>> properties = problematic2properties.get(p);
        if (properties == null) {
            return Collections.<Range>emptyList();
        }
        List<Range> l = properties.get(propertyId);
        if (l == null) {
            return Collections.<Range>emptyList();
        }
        return l;
    }

    public Set<String> getPropertyIds(ProblemToken p) {
    	Map<String, List<Range>> properties = problematic2properties.get(p);
        if (properties == null) {
            return Collections.<String>emptySet();
        }
        return properties.keySet();
    }

    public boolean outlineWholeToken(ProblemToken p) {
        for (String propertyId : getPropertyIds(p)) {
            if (! getPropertyFeedback(p, propertyId).isEmpty()) {
                return false;
            }
        }
        return true;
    }

    protected void add(ProblemToken p, String propertyId, int begin, int end) {
    	Map<String, List<Range>> properties = problematic2properties.get(p);
        if (properties == null) {
            properties = new HashMap<String, List<Range>>();
            problematic2properties.put(p, properties);
        }
        List<Range> l = properties.get(propertyId);
        if (l == null) {
            l = new LinkedList<Range>();
            properties.put(propertyId, l);
        }
        Range range = new Range(begin, end, getSeverity());
        l.add(range);
    }

	/**
	 * Must return description of problem that will be used to create IMarker.MESSAGE
	 * @return
	 */
	public abstract String getDescription();

	/**
	 * Must return problem severity that will be used to create IMarket.SEVERITY
	 * @return
	 */
	public abstract int getSeverity();
	
	/**
	 * Returns true if problem has resolution
	 * @return
	 */
	public abstract boolean hasResolutions();

	/**
	 * Returns problem resolutions
	 * @return
	 */
	public abstract IMarkerResolution[] getResolutions();

    public static class Range {
        public final int begin;
        public final int end;
        public final int severity;

        public Range(int begin, int end, int severity) {
            this.begin = begin;
            this.end = end;
            this.severity = severity;
        }
    }
}
